/**
	JSON parsing and stringifying with support for id and path based referencing,
	by Kris Zyp - www.xucia.com,
	with modifications by Seth Wessitsh (seth@wessitsh.com).

	Uses the "old" JSON JS API that does not break enumerability.
	Modified version of the Douglas Crockford's JSON library from http://www.json.org/js.html
	Modified Kris' version to put fn's in the JSON.ref namespace, to include JSON.ref.resolve, and
		added versions in the schema namespace (sw)
	Public Domain? How are you supposed to license something when you modify public domain code?

	Usage: 
		JSON.ref.stringify(value) 
			turns a JS value into JSON string. When an object is encountered more
			than once (through circular or multiple referencing), a reference is made to it.

		JSON.ref.parse(string) 
			turns a JSON string into a JS object. When a reference is encountered it is resolved.

		JSON.ref.resolve(object) 
			resolves the references found in a JS object.
		
		JSON.schema.ref.parse(string)
			If JSON.schema.registry is defined this works the same as JSON.ref.parse(string) but 
			after a serialization is parsed if it has an id the corresponding object will be added 
			to the registry so it can be used as a type.  If the registry is not present then
			this is just a reference to JSON.ref.parse

		JSON.schema.ref.resolve(object)
			If JSON.schema.registry is defined this works the same as JSON.ref.resolve(object) but 
			adds objects with id's to the registry so they can be used as types.  If the registry
			is not present then this is just a reference to JSON.ref.resolve

	References are in the form of:
	{"$ref":"$|id.path"}
*/
var JSON = JSON ? JSON : {};

JSON.schema = JSON.schema ? JSON.schema : {};

(function() {
	var ids={};

	ids.__add__ = function add(id, value) {
		this[id] = value;
	};

	JSON.ref = {
		err: {
			resolveErr: function(ref) {
				throw new Error("Reference to " + ref + " could not be resolved");
			}
		},
		stringify: function (v) {
			var a = [];
			var paths={};
			var m = {
				'\b': '\\b',
				'\t': '\\t',
				'\n': '\\n',
				'\f': '\\f',
				'\r': '\\r',
				'"' : '\\"',
				'\\': '\\\\'
			};
			function e(s) {
				a[a.length] = s;
			}
			function s(x) {
				if (/["\\\x00-\x1f]/.test(x)) {
					return '"' + x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
						var c = m[b];
						if (c) {
							return c;
						}
						c = b.charCodeAt();
						return '\\u00' +
							Math.floor(c / 16).toString(16) +
							(c % 16).toString(16);
					}) + '"';
				}
				return '"' + x + '"';
			}
			function g(x,p) {
				var i, l, v;
				switch (typeof x) {
					case 'object':
						if (x) {
							if (x instanceof Date) {
								var f = function f(n) {
									return n < 10 ? '0' + n : n;
								};

								return e('"' + this.getUTCFullYear() + '-' +
								       f(this.getUTCMonth() + 1) + '-' +
									   f(this.getUTCDate()) + 'T' +
									   f(this.getUTCHours()) + ':' +
									   f(this.getUTCMinutes()) + ':' +
									   f(this.getUTCSeconds()) + 'Z"');
							}
							// ref ->
							if (x.id) {
								if (ids[x.id] || paths[x.id]) {
									return g({$ref:x.id});
								} else {
									ids[p = x.id] = x;
								}
							} else {
								x.id = p;
								paths[p] = x;
							}
							// <- ref
							if (x instanceof Array) {
								e('[');
								l = a.length;
								for (i = 0; i < x.length; i += 1) {
									v = x[i];
									if (
										typeof v != 'undefined' &&
										typeof v != 'function'
									) {
										if (l < a.length) {
											e(',');
										}
										g(v,p +'['+i+']');
									}
								}
								e(']');
								return;
							} else if (typeof x.valueOf == 'function') {
								e('{');
								l = a.length;
								for (i in x) {
									v = x[i];
									if (
										typeof v != 'undefined' && !(i == 'id' && paths[v]) &&
										typeof v != 'function' &&
										(!v || typeof v != 'object' || typeof v.valueOf == 'function')
									) {
										if (l < a.length) {
											e(',');
										}
										e(s(i));
										e(':');
										// -> ref
										g(v,p+(i.match(/^[a-zA-Z]\w*$/) ? ('.' + i) : ('[' + s(i) + ']')));
										// <- ref
									}
								}
								return e('}');
							}
						}
						e('null');
					return;
					case 'number':
						e(isFinite(x) ? +x : 'null');
					return;
					case 'string':
						e(s(x));
					return;
					case 'boolean':
						e(String(x));
					return;
					default:
						e('null');
					return;
				}
			}
			g(v,'$');
			for (var i in paths) {
				delete paths[i].id;   
			}
			return a.join('');
		},
		parse : function(str,stop) {
			if (
				/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/.test(
					str.replace(new RegExp("\\\\.","g"), '@')
					   .replace(new RegExp("\"[^\"\\\n\r]*\"", "g"), '')
				)
			) {
				var j = eval('(' + str + ')');
				return this.resolve(j,stop);
			}
		},
		resolve : function(j, stop) {
			var ref,rw=[];
			function walk(v,stop) {
				var val;
				// ref ->
				if (v.id) {
					ids.__add__(v.id, v);
				}

				for (var i in v) {
					if (v.hasOwnProperty(i) && (typeof (val=v[i]) =='object') && val) {
						if ((ref=val.$ref)) {
							var stripped = ref.replace(new RegExp("\\\\.","g"), '@').replace(new RegExp("\"[^\"\\\n\r]*\"", "g"), '');
							if (/[\w\[\]\.\$ \/\r\n\t]/.test(stripped) && !(new RegExp("=|((^|\\W)new\\W)")).test(stripped)) {
								var path = ref.match(new RegExp("(^[$\\w]+)(\\W.*)?"));
								(ref=path[1]=='$'?j:ids[path[1]]) ?
									(v[i] = path[2] ? eval('ref' + path[2]) : ref) : 
									(stop ? JSON.ref.err.resolveErr(val.$ref) : rw.push(v)); 
							}
						}
						if (!stop) { // <- ref
							walk(val,rw==v);
						}
					}
				}
			}

			walk(j);
			walk(rw);
			return j;
//			throw new SyntaxError('parseJSON');		
		}
	};

	if ("registry" in JSON.schema) {
		JSON.schema.ref =  {
			parse: function parse(str) {
				var idAddFn = ids.__add__;
				ids.__add__ = function add(id, value) {
					this[id] = value;
					JSON.schema.registry[id] = value;
				};
	
				JSON.ref.parse(str);
	
				ids.__add__ = idAddFn;
			},
			resolve: function resolve(j) {
				var idAddFn = ids.__add__;
	
				ids.__add__ = function add(id, value) {
					this[id] = value;
					JSON.schema.registry[id] = value;
				};
	
				JSON.ref.resolve(j);
	
				ids.__add__ = idAddFn;
			}
		};
	} else {
		JSON.schema.ref = {
			parse: JSON.ref.parse,
			resolve: JSON.ref.resolve
		};
	}
}());
